/**
 * \file   mm.cpp
 * \author Fabian Jesus Machado
 * \date   Septiembre de 2011
 * 
 *  Este archivo es parte del proyecto NPASM, un ensamblador para
 * microcontroladores PIC con sintaxis Intel, desarrollado inicialmente
 * por Fabian Machado (fabianjsm@gmail.com, twitter@fabianjsm).
 *  El codigo fuente esta diponible en el repositorio SVN de Google:
 *      http://npasm.googlecode.com/svn/trunk.
 * 
 * Las funciones de este archivo administran la memoria.
 * 
 */
#include <iostream>
#include "mm.h"
#include "auxiliar.h"
#include "sintaxis.h"
#include "entorno.h"

using namespace std;

/* Descripcion de memoria establecida por defecto */
const char *lnk =
	"rom code:   0x0000, 0x1000;\n"
	"ram data:   0x0020, 0x0100;\n"
	"rom config: 0x2007, 0x2009;\n"
;

void copiar_seccion(class_seccion *s, class_seccion *t)
{
#define CP_SEC(PROP) s->PROP = t->PROP
	CP_SEC(tipo);
	CP_SEC(base);
	CP_SEC(p);
	CP_SEC(maxp);
}

/**
 * Constructor de la clase class_seccion
 */
class_seccion::class_seccion(int _tipo, size_t _base, size_t _maxp)
{
	tipo = _tipo;
	base = _base;
	maxp = _maxp;
	p = base;
}

int mm::get()
{
	return toupper(lnk[lexp++]);
}

void mm::unget()
{
	if(lexp)
		--lexp;
}

void mm::NuevaSeccion(std::string nombre, class_seccion *s)
{
	unsigned int n = vseccion.size();
	simbolo *sbl = Entorno->TS->Buscar(nombre);
	if(sbl)
		throw(mkstring("'%s' no puede ser el nombre de una seccion",
			nombre.c_str()));
	sbl = Entorno->TS->Insertar(nombre);
	sbl->SetTipo(SECCION);
	sbl->SetVal((unsigned int) n);
	sbl->SetUcase(true);
	vseccion.push_back(class_seccion(s->tipo, s->base, s->maxp));
}

/**
 * Cargar el archivo que describe los segmentos de memoria
 */
#define LNK_IDENT 0x100
#define LNK_NUM   0x101
#define LNK_RAM   0x102
#define LNK_ROM   0x103
void mm::CargarLnk()
{
	lexp=0;
	string texto;
	int c=1, tk=0, e=0, ln=1;
	unsigned long num=0;
	string nombre;
	class_seccion s(0,0,0);
	
	/* *****************************************************************
	 * Eliminar secciones anteriores
	 */
	vseccion.clear();
	
	try {
		while(c) {
			/* *********************************************************
			 * Lexico
			 */
			while((c = get())==' ' || c=='\t' || c=='\n' || c=='\r')
				if(c == '\n')
					ln++;
			texto.clear();
			if(!c)
				break;
			else if(isalpha(c) || c == '.') {
				do {
					texto += c;
					c = get();
				} while(isalpha(c));
				unget();
				tk = LNK_IDENT;
			} else if(c > '0' && c <= '9') {
				do {
					texto += c;
					c = get();
				} while(isdigit(c));
				unget();
				tk = LNK_NUM;
			} else if(c == '0') {
				c = get();
				if(c == 'X') {
					texto = "0x0";
					while(isxdigit(c = get()))
						texto += c;
				} else if(c == 'B') {
					texto = "0B0";
					while((c = get()) == '0' || c == '1')
						texto += c;
				}
				unget();
				tk = LNK_NUM;
			} else if(isdigit(c)) {
				do {
					texto += c;
					c = get();
				} while(isdigit(c));
				unget();
				tk = LNK_NUM;
			} else if(c == '#') {
				while((c = get()) != 0) {
					if(c == '\n') {
						ln++;
						break; // while
					}
				}
				if(!c)
					break; // c==0, sale del for
				continue; // for
			} else
				tk = c;
			
			/* *********************************************************
			 * Sintaxis
			 */
			struct {
				int tk;          // esperado
				const char *msj; // mensaje de error
				bool continuar;  // ejecutar un continue?
			} esperado[] = {
				{LNK_IDENT, "RAM o ROM",         false}, // 0
				{LNK_IDENT, "nombre de seccion", false}, // 1
				{':',       "dos puntos",        true }, // 2
				{LNK_NUM,   "numero",            false}, // 3
				{',',       "coma",              true }, // 4
				{LNK_NUM,   "numero",            false}, // 5
				{';',       "punto y coma",      false}  // 6
			};
			if(tk != esperado[e].tk)
				throw(mkstring("se esperaba %s", esperado[e].msj));
			if(esperado[e].continuar) {
				++e;
				continue;
			}
			if(tk == LNK_NUM)
				num = strtoulong(texto.c_str(), 0);
			switch(e) {
			case 0: // tipo
				if(texto == "RAM")
					s.tipo = RAM;
				else if(texto == "ROM")
					s.tipo = ROM;
				else
					throw(string("se esperaba RAM o ROM"));
				break;
			case 1: // nombre
				nombre = texto;
				break;
			case 3: // limite inferior
				s.base = num;
				break;
			case 5: // limite superior
				s.maxp = num;
				break;
			case 6: // crear la seccion
				if(s.maxp < s.base)
					throw(string("discordancia de limites"));
				NuevaSeccion(nombre, &s);
			}
			if(++e == 7)
				e=0;
		}
		if(e)
			throw(string("final prematuro"));
		/* *************************************************************
		 * Determinar la longitud de la memoria
		 */
		size_t nrom=0, nram=0, npos = vseccion.size();
		size_t seccion_rom=0; /* primer seccion ROM que encontramos */
		for(size_t i=0; i < npos; i++) {
			class_seccion &s = vseccion[i];
			if(s.tipo == ROM) {
				if(!nrom)
					seccion_rom = i; /* primer seccion ROM */
				if(s.maxp > nrom)
					nrom = s.maxp;
			} else if((s.tipo == RAM) && (s.maxp > nram))
				nram = s.maxp;
		}
		if(!nrom)
			throw(string("imposible determinar longitud de memoria ROM"));
		rom.resize(nrom);
		for(size_t i=0; i<nrom; i++)
			rom[i] = NULL_ROM;
		seccion = NO_SECCION;
		/* ******************
		 * Seleccionamos la seccion ROM predefinida
		 */
		SetSeccion(seccion_rom);
	}
	catch(string err)
	{
		throw(err + mkstring(" en linea %d del script lnk", ln));
	}
}

/**
 * Constructor de la clase mm
 */
mm::mm(): class_seccion(ROM, 0, 0xFFF)
{
}

void mm::ReiniciarPunteros()
{
	size_t n = vseccion.size();
	for(size_t i=0; i < n; i++)
		vseccion[i].p = vseccion[i].base;
	p = base;
}

void mm::SetSeccion(int n)
{
	if(seccion != NO_SECCION)
		copiar_seccion(&vseccion[seccion], this);
	copiar_seccion(this, &vseccion[n]);
	seccion=n;
}

/**
 * Destructor de la clase mm
 */
mm::~mm()
{
}

void mm::SetROM(unsigned short _op)
{
	if(tipo == RAM)
		throw(string("no se puede generar codigo sobre esta seccion"));
	if(p == maxp)
		throw(string("escritura sobre posicion de memoria prohibida"));
	rom[p++]=_op;
}

std::vector<unsigned short> &mm::GetVectorROM()
{
	return rom;
}

size_t mm::GetP()
{
	return p;
}

void mm::SetP(size_t _p)
{
	p = _p;
}

void mm::AvanzarP(size_t _i)
{
	if(p + _i > maxp)
		throw(string("fuera del segmento de memoria"));
	p += _i;
}

void mm::Alinear(size_t _n)
{
	size_t bits=0, n, m;
	for(n = _n; n; n >>= 1)
		if(n & 1)
			++bits;
	if(bits != 1)
		throw(mkstring("la alineacion 0x%02X es ilegal", _n));
	m = _n - 1; /* mascara */
	if(!(p & m))
		return; /* ya esta alineado */
	size_t prev_p = p;
	p |= m;
	++p;
	while(prev_p < p)
		rom[prev_p++] = 0; /* NOP */
}
